﻿#include "CPayplatProxy.h"
#include <QXmlStreamReader>
#include <QDateTime>
#include <QNetworkProxy>
#include <QEventLoop>
#include <QTimerEvent>
#include "glog/logging.h"

#define XML_ELE_RETURN              "return"
#define XML_ELE_ZMSG                "ZMSG"
#define XML_ELE_ZHEAD               "ZHEAD"
#define XML_ELE_BCODE               "bcode"
#define XML_ELE_ISTART              "istart"
#define XML_ELE_IEND                "iend"
#define XML_ELE_IFLAG               "iflag"
#define XML_ELE_TCODE               "tcode"
#define XML_ELE_RETCODE             "retcode"
#define XML_ELE_RETMSG              "retmsg"
#define XML_ELE_TOTNUM              "totnum"
#define XML_ELE_CURNUM              "curnum"
#define XML_ELE_ZBODY               "ZBODY"
#define XML_ELE_PAYURL              "pay_url"
#define XML_ELE_QRCODE              "qr_code"
#define XML_ELE_IMGURL              "img_url"
#define XML_ELE_ORDERID             "order_id"
#define XML_ELE_OUTTRADENO          "out_trade_no"
#define XML_ELE_PREPAYID            "prepay_id"
#define XML_ELE_TOTALFEE            "total_fee"
#define XML_ELE_PAYTYPE             "pay_type"
#define XML_ELE_PRODUCTID           "product_id"
#define XML_ELE_BOXID               "box_id"
#define XML_ELE_LIMITTIME           "limit_time"
#define XML_ELE_PAYSTATE            "pay_state"
#define XML_ELE_PAYTIME             "pay_time"
#define XML_ELE_RESPTIME            "resp_time"
#define XML_ELE_AUTHNAME            "auth_name"
#define XML_ELE_AUTHID              "auth_id"
#define XML_ELE_SITEID              "site_id"
#define XML_ELE_SITENAME            "site_name"
#define XML_ELE_CORPID              "corp_id"
#define XML_ELE_CORPNAME            "corp_name"
#define XML_ELE_OPENID              "openid"
#define XML_ELE_ORDERTIME           "order_time"
#define XML_ELE_ORDERDESC           "order_desc"
#define XML_ELE_CUSTNO              "cust_no"
#define XML_ELE_EMAIL               "email"
#define XML_ELE_MOBILE              "mobile"
#define XML_ELE_NAME                "name"
#define XML_ELE_USERACCT            "user_acct"
#define XML_ELE_ATTACH              "attach"
#define XML_ELE_CURRENCYTYPE        "currency_type"
#define XML_ELE_FEETYPE             "fee_type"

#define CUR_DATETIME            QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss")
#define REPLY_USERID            0x0001;
#define PAY_REQUEST_TYPE        (QNetworkRequest::Attribute)(QNetworkRequest::User + 1)
#define PAY_REQUEST_ORDERID     (QNetworkRequest::Attribute)(QNetworkRequest::User + 2)

// factory
zl::IPayplatInterface* zl::GetPayplatInterface()
{
    return CPayplatProxy::Instance();
}

void zl::ReleasePayplatInterface(zl::IPayplatInterface* ptr)
{
    Q_UNUSED(ptr)
    CPayplatProxy::Release();
}

// CPayplatProxy
CPayplatProxy* CPayplatProxy::instance_ = Q_NULLPTR;
CPayplatProxy* CPayplatProxy::Instance()
{
    if (instance_ == Q_NULLPTR)
    {
        instance_ = new CPayplatProxy;
    }

    return instance_;
}

void CPayplatProxy::Release()
{
    delete instance_;
}

CPayplatProxy::CPayplatProxy()
    : mgr_(new QNetworkAccessManager(this))
    , obs_(nullptr)
    , request_timer_(-1)
{
    request_timer_ = startTimer(1000);

    // connect
    connect(mgr_, SIGNAL(finished(QNetworkReply*)),
            this, SLOT(on_network_request_finished(QNetworkReply*)),
            Qt::QueuedConnection);

    qRegisterMetaType<zl::PayRequest>("zl::PayRequest");
    connect(this, SIGNAL(sendRequest(const zl::PayRequest)),
            this, SLOT(send_request(const zl::PayRequest)),
            Qt::QueuedConnection);
}

CPayplatProxy::~CPayplatProxy()
{

}

int32_t CPayplatProxy::Initialize(const zl::PayplatConfig &config, zl::IPayplatObserver *obs)
{
    if (obs != nullptr)
    {
        obs_ = obs;
    }

    config_ = config;

    return 0;
}

void CPayplatProxy::Uninitialize()
{
    obs_ = nullptr;
    config_.plat_url.clear();
}

int32_t CPayplatProxy::EnableOneCode(bool flag)
{
    enable_onecode_ = flag;
    return 0;
}

int32_t CPayplatProxy::CreateOrder(const zl::PayRequest &req)
{
    zl::PayRequest tmp = req;
//    tmp.pay_type = enable_onecode_ ? "09" : req.pay_type;
    tmp.pay_type = req.pay_type;
    emit sendRequest(tmp);
    return 0;
}

int32_t CPayplatProxy::CheckOrder(const zl::PayRequest &req)
{
    emit sendRequest(req);
    return 0;
}
int32_t CPayplatProxy::FindOrder(const zl::PayRequest &req)
{
    emit sendRequest(req);
    return 0;
}

int32_t CPayplatProxy::GetQRCode(const std::string &url, char **buf, int32_t &size)
{
    QNetworkAccessManager manager;
    QEventLoop loop;


    QNetworkRequest req(QUrl(url.c_str()));
    req.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);


    QNetworkReply *reply = manager.get(req);
    QObject::connect(reply, SIGNAL(finished()), &loop, SLOT(quit()));
    loop.exec();
    QByteArray data = reply->readAll();

    size = data.size();
    void* ptr = malloc(sizeof(char) * size);
    memcpy(ptr, data.data(), size);
    *buf = (char*)ptr;

    return 0;
}

std::string CPayplatProxy::create_request_body(const zl::PayRequest &req)
{
    QString str = QString("")
        + "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
        + "<SOAP-ENV:Envelope"
        + " xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\""
        + " xmlns:SOAP-ENC=\"http://schemas.xmlsoap.org/soap/encoding/\""
        + " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\""
        + " xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\""
        + " xmlns:ns1=\"http://post.lmxf.com\">"
        + "<SOAP-ENV:Body>"
        + "<ns1:payApply>"
        + "<arg0>" + QString(config_.auth_id.c_str()) + "</arg0>"
        + "<arg1>" + QString(config_.auth_name.c_str()) + "</arg1>"
        + "<arg2>" + QString(create_request_content(req).c_str()) + "</arg2>"
        + "</ns1:payApply>"
        + "</SOAP-ENV:Body>"
        + "</SOAP-ENV:Envelope>";

    return str.toStdString();
}

std::string CPayplatProxy::create_request_content(const zl::PayRequest &req)
{
    QString content;
    content.clear();
    if(req.type == zl::EPayRequestTypeCreate)
    {
        content = QString("")
            + "<ZMSG><ZHEAD>"
            + "<bcode>05</bcode>"
            + "<istart>1</istart>"
            + "<iend>1</iend>"
            + "<iflag>1</iflag>"
            + "<tcode>2600</tcode>"
            + "</ZHEAD><ZBODY>"
            + "<auth_name>" + QString(config_.auth_name.c_str()) + "</auth_name>"
            + "<auth_id>" + QString(config_.auth_id.c_str()) + "</auth_id>"
            + "<site_name>" + QString(config_.site_name.c_str()) + "</site_name>"
            + "<site_id>" + QString(config_.site_id.c_str()) + "</site_id>"
            + "<corp_name>" + QString(config_.corp_name.c_str()) + "</corp_name>"
            + "<corp_id>" + QString(config_.corp_id.c_str()) + "</corp_id>"
            + "<order_id>" + QString(req.order_id.c_str()) + "</order_id>"
            + "<prepay_id>" + QString(req.prepay_id.c_str()) + "</prepay_id>"
            + "<pay_type>" + QString(req.pay_type.c_str()) + "</pay_type>"
            + "<out_trade_no></out_trade_no>"
            + "<currency_type>CNY</currency_type>"
            + "<fee_type>00</fee_type>"
            + "<total_fee>" + QString(req.total_fee.c_str()) + "</total_fee>"
            + "<openid></openid>"
            + "<order_time>" + CUR_DATETIME + "</order_time>"
            + "<order_desc>dcf</order_desc>"
            + "<cust_no>134103100</cust_no>"
            + "<email>zhengzhiwu@szzhilai.com</email>"
            + "<mobile>134103100</mobile>"
            + "<name>zhiwuzheng</name>"
            + "<user_acct>134103100</user_acct>"
            + "<attach>201801</attach>"
            + "</ZBODY></ZMSG>";
    }
    else
    {
        content = QString("")
            + "<ZMSG><ZHEAD>"
            + "<bcode>03</bcode>"
            + "<istart>1</istart>"
            + "<iend>1</iend>"
            + "<iflag>1</iflag>"
            + "<tcode>2631</tcode>"
            + "</ZHEAD><ZBODY>"
            + "<auth_name>" + QString(config_.auth_name.c_str()) + "</auth_name>"
            + "<auth_id>" + QString(config_.auth_id.c_str()) + "</auth_id>"
            + "<site_name>" + QString(config_.site_name.c_str()) + "</site_name>"
            + "<site_id>" + QString(config_.site_id.c_str()) + "</site_id>"
            + "<corp_name>" + QString(config_.corp_name.c_str()) + "</corp_name>"
            + "<corp_id>" + QString(config_.corp_id.c_str()) + "</corp_id>"
            + "<prepay_id>" + QString(req.prepay_id.c_str()) + "</prepay_id>"
            + "<order_id>" + QString(req.order_id.c_str()) + "</order_id>"
            + "<pay_type>" + QString(req.pay_type.c_str()) + "</pay_type>"
            + "<out_trade_no>" + QString(req.trade_no.c_str()) + "</out_trade_no>"
            + "<order_time>" + CUR_DATETIME + "</order_time>"
            + "</ZBODY></ZMSG>";
        }

        LOG(INFO) << "pay plat send content:" << content.toStdString();
        content.replace(">", "&gt;");
        content.replace("<", "&lt;");
        content.replace("\n", "&#xA;");
        content.replace("\"", "&quot;");
        return content.toStdString();
}

zl::EPayError CPayplatProxy::convert_http_status_code(int code)
{
    switch (code)
    {
    case 400:   return zl::EPayError_400;
    case 404:   return zl::EPayError_404;
    case 500:   return zl::EPayError_500;
    case 502:   return zl::EPayError_502;
    default:
        return zl::EPayError_NetworkError;
    }
}

std::string CPayplatProxy::get_reply_body(QByteArray body)
{
    QString content = "";
    QXmlStreamReader xml(body);
    while (!xml.atEnd())
    {
        xml.readNext();
        if (xml.isStartElement())
        {
            if (xml.name().toString() == XML_ELE_RETURN)
            {
                content = xml.readElementText();
                break;
            }
        }
    }

    if (xml.hasError())
    {
        return "";
    }

    return content.toStdString();
}

zl::EPayError CPayplatProxy::process_reply_body(const std::string body, zl::PayReply& rep)
{
    QString content = body.c_str();
    content.replace("&lt;", "<");
    content.replace("&gt;", ">");
    content.replace("&#xA;", "\n");
    content.replace("&#xa;", "\n");
    content.replace("&quot;", "\"");

    QXmlStreamReader xml(content);
    while (!xml.atEnd())
    {
        xml.readNext();
        if (!xml.isStartElement())
        {
            continue;
        }

        QString elename = xml.name().toString();
        if (elename == XML_ELE_RETCODE)
        {
            rep.retcode = xml.readElementText().toStdString();
        }
        else if (elename == XML_ELE_RETMSG)
        {
            rep.retmsg = xml.readElementText().toStdString();
        }
        else if (elename == XML_ELE_TOTNUM)
        {
            rep.totnum = xml.readElementText().toStdString();
        }
        else if (elename == XML_ELE_CURNUM)
        {
            rep.curnum = xml.readElementText().toStdString();
        }
        else if (elename == XML_ELE_IMGURL)
        {
            rep.img_url = xml.readElementText().toStdString();
        }
        else if (elename == XML_ELE_ORDERID)
        {
            rep.order_id = xml.readElementText().toStdString();
        }
        else if (elename == XML_ELE_OUTTRADENO)
        {
            rep.out_trade_no = xml.readElementText().toStdString();
        }
        else if (elename == XML_ELE_PREPAYID)
        {
            rep.prepay_id = xml.readElementText().toStdString();
        }
        else if (elename == XML_ELE_TOTALFEE)
        {
            rep.total_fee = xml.readElementText().toStdString();
        }
        else if (elename == XML_ELE_PAYTYPE)
        {
            rep.pay_type = xml.readElementText().toStdString();
        }
        else if (elename == XML_ELE_PRODUCTID)
        {
            rep.product_id = xml.readElementText().toStdString();
        }
        else if (elename == XML_ELE_BOXID)
        {
            // TODO
        }
        else if (elename == XML_ELE_LIMITTIME)
        {
            rep.limit_time = xml.readElementText().toStdString();
        }
        else if (elename == XML_ELE_PAYSTATE)
        {
            rep.pay_state = xml.readElementText().toStdString();
        }
        else if (elename == XML_ELE_PAYTIME)
        {
            rep.pay_time = xml.readElementText().toStdString();
        }
        else if (elename == XML_ELE_RESPTIME)
        {
            rep.resp_time = xml.readElementText().toStdString();
        }
    }

    if (xml.hasError())
    {
        return zl::EPayError_ParseXMLError;
    }

    return zl::EPayError_Success;
}

void CPayplatProxy::timerEvent(QTimerEvent *e)
{
    if (e->timerId() == request_timer_)
    {
        request_map_mutex_.lock();
        auto itbeg = request_map_.begin();
        auto itend = request_map_.cend();
        while (itbeg != itend)
        {
            itbeg->second.ttl -= 1;
            if (itbeg->second.ttl <= 0)
            {
                zl::PayReply rep;
                rep.order_id = itbeg->second.request.order_id;
                rep.type = itbeg->second.request.type;
                if (obs_ != nullptr)
                {
                    obs_->PayplatReply(rep, zl::EPayError_Timeout);
                }
                request_map_.erase(itbeg++);
                continue;
            }
            itbeg++;
        }
        request_map_mutex_.unlock();
    }

    QObject::timerEvent(e);
}

void CPayplatProxy::send_request(const zl::PayRequest request)
{
    if (request_map_.find(request.order_id) != request_map_.end())
    {
        return;
    }
    request_map_mutex_.lock();
    request_map_.insert(std::make_pair(request.order_id, MapRequest(request, config_.timeout)));
    request_map_mutex_.unlock();

    std::string body = create_request_body(request);
    QNetworkRequest req;
    req.setRawHeader("User-Agent", "gSoap/2.8");
    req.setRawHeader("Content-Type", "application/xml");
    req.setUrl(QUrl(config_.plat_url.c_str()));
    req.setAttribute(PAY_REQUEST_ORDERID, request.order_id.c_str());
    req.setAttribute(PAY_REQUEST_TYPE, request.type);

    QNetworkReply* rep = mgr_->post(req, body.c_str());
    if (rep == Q_NULLPTR)
    {
        zl::PayReply reply;
        reply.order_id = request.order_id;
        reply.type = request.type;
        if (obs_ != nullptr)
        {
            obs_->PayplatReply(reply, zl::EPayError_Failed);
        }
        return;
    }
}

void CPayplatProxy::on_network_request_finished(QNetworkReply *reply)
{
    const QNetworkRequest rep_request = reply->request();
    int code = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
    int type = rep_request.attribute(PAY_REQUEST_TYPE).toInt();
    std::string orderid = rep_request.attribute(PAY_REQUEST_ORDERID).toString().toStdString();

    if (request_map_.find(orderid) == request_map_.end())
    {
        reply->abort();
        reply->deleteLater();
        return;
    }
    request_map_mutex_.lock();
    request_map_.erase(orderid);
    request_map_mutex_.unlock();

    zl::PayReply rep;
    rep.order_id = orderid;
    rep.type = type;

    if (code != 200)
    {
        if (obs_ != nullptr)
        {
            obs_->PayplatReply(rep, convert_http_status_code(code));
        }
        reply->abort();
        reply->deleteLater();
        return;
    }

    std::string body = get_reply_body(reply->readAll());
    if (body.empty())
    {
        if (obs_ != nullptr)
        {
            obs_->PayplatReply(rep, zl::EPayError_ParseXMLError);
        }
        reply->abort();
        reply->deleteLater();
        return;
    }

    LOG(INFO) << "pay plat recv:" << body.c_str();
    int32_t ret = process_reply_body(body, rep);
    if (obs_ != nullptr)
    {
        obs_->PayplatReply(rep, ret);
    }
    reply->abort();
    reply->deleteLater();
}
